<script lang="ts">
	import { onMount, onDestroy, untrack, tick } from 'svelte';

	let {
		children,
		data,
		itemHeight,
		bufferSize,
		scrollTop,
		styles,
		height,
		gap,
		scrollContainerRef = $bindable(),
		scrollTypeX,
		scrollTypeY,
		onScroll,
		onScrollPositionChange,
		onScrollbarChange,
	} = $props<{
		children?: (context: {
			visibleData: any[];
			visibleStartIndex: number;
			visibleEndIndex: number;
			totalHeight: number; // 虽然用不到，但保持接口兼容
		}) => any;
		data: any[];
		itemHeight: number;
		bufferSize?: number;
		scrollTop?: number;
		styles?: string;
		height?: string;
		gap?: number;
		scrollContainerRef?: HTMLElement | null;
		scrollTypeX?: 'scroll' | 'auto' | 'hidden';
		scrollTypeY?: 'scroll' | 'auto' | 'hidden';
		onScroll?: (e: Event) => void;
		onScrollPositionChange?: (position: { scrollTop: number; scrollLeft: number }) => void;
		onScrollbarChange?: (
			hasVerticalScroll: boolean,
			scrollbarWidth: number,
			hasHorizontalScroll: boolean,
			scrollbarHeight: number
		) => void;
	}>();

	// 状态管理
	let containerHeight = $state(0);
	let visibleScrollTop = $state(scrollTop ?? 0);
	let hasVerticalScroll = $state(false);
	let hasHorizontalScroll = $state(false);

	// 检测滚动条的核心逻辑
	const checkScrollbar = () => {
		if (!scrollContainerRef) return;

		// 允许 1px 的误差
		const hasVert = totalHeight > scrollContainerRef.clientHeight;
		const hasHoriz = scrollContainerRef.scrollWidth > scrollContainerRef.clientWidth;

		if (hasVert !== hasVerticalScroll || hasHoriz !== hasHorizontalScroll) {
			hasVerticalScroll = hasVert;
			hasHorizontalScroll = hasHoriz;
			// 等待 DOM 更新
			// await tick();
			const vWidth = hasVert ? scrollContainerRef.offsetWidth - scrollContainerRef.clientWidth : 0;

			const hHeight = hasHoriz
				? scrollContainerRef.offsetHeight - scrollContainerRef.clientHeight
				: 0;

			onScrollbarChange?.(hasVert, vWidth, hasHoriz, hHeight);
		}
	};

	// 这里的 visibleStartIndex 改为“实际渲染的起始索引”（包含缓冲区）
	let renderStartIndex = $state(0);
	let renderEndIndex = $state(0);

	let offsetY = $state(0);

	// 计算属性
	const dataLength = $derived(data.length);
	const gapHeight = $derived(gap ?? 0);
	const itemTotalHeight = $derived(itemHeight + gapHeight);

	// 动态计算缓冲区：增加缓冲区大小以应对缩放和快速滚动
	const actBufferSize = $derived(bufferSize ?? (data.length > 1000 ? 20 : 10));

	// 总高度
	const totalHeight = $derived(
		Math.max(
			0,
			dataLength * itemHeight + (dataLength > 0 ? (dataLength - 1) * gapHeight : 0)
		)
	);

	// 核心计算逻辑
	const updateVisibleRange = (input: string) => {
		if (!scrollContainerRef) return;

		// 1. 计算视口内的第一个元素的索引
		const startIndex = Math.floor(visibleScrollTop / itemTotalHeight);

		// 2. 计算视口内能容纳的元素数量 (向上取整防止留白)
		const visibleCount = Math.ceil(containerHeight / itemTotalHeight);

		// 3. 计算渲染范围（包含顶部和底部缓冲区）
		// 关键优化：向前多渲染 actBufferSize 个，防止向上滚动时白屏/抖动
		const start = Math.max(0, startIndex - actBufferSize);
		const end = Math.min(dataLength, startIndex + visibleCount + actBufferSize);

		renderStartIndex = start;
		renderEndIndex = end;

		// 4. 计算偏移量
		// 关键优化：偏移量应该基于“实际渲染的第一个元素”，而不是视口第一个元素
		// 这样在滚动经过 buffer 区域时，translateY 是稳定的
		offsetY = start * itemTotalHeight;
		// console.log(
		// 	'renderStartIndex',
		// 	input,
		// 	renderStartIndex,
		// 	renderEndIndex,
		// 	offsetY,
		// 	visibleScrollTop
		// );
	};

	// 处理滚动
	const handleScroll = (e: Event) => {
		const target = e.target as HTMLElement;
		// 使用 requestAnimationFrame 保证在下一帧渲染前更新，避免高频抖动
		requestAnimationFrame(() => {
			visibleScrollTop = target.scrollTop;
			onScroll?.(e);
			onScrollPositionChange?.({ scrollTop: visibleScrollTop, scrollLeft: target.scrollLeft });
		});
	};

	// 处理 Resize
	// 优化：移除 debounce。
	// 当用户拖拽调整大小时，内容必须 1:1 实时跟随容器变化，否则会出现“容器变了内容没变”的视觉错位抖动。
	const handleResize = (entries: ResizeObserverEntry[]) => {
		for (let entry of entries) {
			// 使用 contentRect 更精确
			const newHeight = entry.contentRect.height;
			if (containerHeight !== newHeight && newHeight > 0) {
				containerHeight = newHeight;
			}
		}
		requestAnimationFrame(checkScrollbar);
	};

	// 数据源变化监听
	$effect(() => {
		visibleScrollTop;
		itemTotalHeight;
		containerHeight;
		dataLength;
		actBufferSize;
		// 不依赖内部追踪，手动触发更新
		untrack(() => {
			updateVisibleRange('effect');
		});
	});

	// 专门监听 scrollTop 变化的 effect (如果父组件控制滚动)，当scrollContainerRef.scrollTop更新时，会自动触发handleScroll
	$effect(() => {
		if (
			typeof scrollTop === 'number' &&
			scrollContainerRef &&
			Math.abs(scrollTop - scrollContainerRef.scrollTop) > 1
		) {
			scrollContainerRef.scrollTop = scrollTop;
			//  updateVisibleRange();
		}
	});

	$effect(() => {
		// 确保容器已经挂载并有高度
		if (scrollContainerRef && containerHeight > 0) {
			// 计算新的最大滚动距离
			const maxScrollTop = Math.max(0, totalHeight - containerHeight);

			// 如果当前的滚动位置超出了新的最大值...
			// 注意：这里我们直接读取 DOM 的 scrollTop，因为它才是“地面实况”
			if (scrollContainerRef.scrollTop > maxScrollTop) {
				//当scrollContainerRef.scrollTop更新时，会自动触发handleScroll
				scrollContainerRef.scrollTop = maxScrollTop;
				// console.log('执行 scrollContainerRef 变化监听');
			}
		}
	});
	$effect(() => {
		totalHeight;
		untrack(() => {
			setTimeout(checkScrollbar, 0);
		});
	});
	let observer: ResizeObserver;
	onMount(() => {
		if (scrollContainerRef) {
			// 初始化高度

			observer = new ResizeObserver(handleResize);
			observer.observe(scrollContainerRef);
		}
	});

	onDestroy(() => {
		observer?.disconnect();
	});

	// 滚动条逻辑
	let showScrollbar = $state(false);
</script>

<div
	class="virtual-scroll"
	role="region"
	onmouseenter={() => (showScrollbar = true)}
	onmouseleave={() => (showScrollbar = false)}
	style={styles}
	style:height={height ?? '100%'}
	style:--scroll-type-x={scrollTypeX}
	style:--scroll-type-y={scrollTypeY}
>
	<!-- 
        优化：contain: strict 告诉浏览器这个容器尺寸独立，减少重排影响
     -->
	<div
		class="virtual-scroll-container"
		bind:this={scrollContainerRef}
		onscroll={handleScroll}
		class:show-scrollbar={showScrollbar || scrollTypeX === 'auto' || scrollTypeY === 'auto'}
		style:contain="strict"
	>
		<div class="virtual-list-phantom" style:height={`${totalHeight}px`}></div>
		<div
			class="virtual-list"
			style:transform={`translate3d(0, ${offsetY}px, 0)`}
			style:will-change="transform"
		>
			<!-- 
					注意：这里传递给 children 的是实际渲染的数据切片 
					如果 data 是大数组，建议这里直接 slice 传给 children，或者传索引让 children 自己取
				-->
			{@render children?.({
				visibleData: data.slice(renderStartIndex, renderEndIndex), // 传切片数据方便使用
				visibleStartIndex: renderStartIndex,
				visibleEndIndex: renderEndIndex,
				totalHeight
			})}
		</div>		
	</div>
</div>

<style lang="scss">
	.virtual-scroll {
		position: relative;
		width: 100%;
		box-sizing: border-box;
		overflow: hidden; /* 防止外部溢出 */
	}

	.virtual-scroll-container {
		position: relative;
		width: 100%;
		height: 100%;
		overflow-x: var(--scroll-type-x, scroll);
		overflow-y: var(--scroll-type-y, scroll);
		/* 移除 padding-bottom 1px hack，这通常会导致高度计算偏差 */
		box-sizing: border-box;

		/* 滚动条样式 */
		&.show-scrollbar::-webkit-scrollbar-thumb {
			background-color: var(--color-info-400, #ccc);
		}
		&.show-scrollbar::-webkit-scrollbar-thumb:hover {
			background-color: var(--color-info-500, #999);
		}
	}

	/* 滚动条基础样式 */
	.virtual-scroll-container::-webkit-scrollbar {
		width: 8px;
		height: 8px;
	}
	.virtual-scroll-container::-webkit-scrollbar-track {
		background-color: transparent;
	}
	.virtual-scroll-container::-webkit-scrollbar-thumb {
		background-color: transparent;
		border-radius: 4px;
		transition: background-color 0.2s ease;
	}
	.virtual-scroll-container::-webkit-scrollbar-corner {
		background-color: transparent;
	}

	.virtual-list-phantom {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		z-index: -1;
		pointer-events: none; /* 关键：避免 phantom 遮挡点击事件 */
		visibility: hidden;
	}

	.virtual-list {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
	}
</style>
